// 版权所有2012 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ast

import (
	"bytes"
	"fmt"
	"go/token"
	"sort"
)

type byPos []*CommentGroup

func (a byPos) Len() int           { return len(a) }
func (a byPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
func (a byPos) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

// sortComments按源代码顺序对注释组列表进行排序。
// 
func sortComments(list []*CommentGroup) {
	// TODO（gri）：首先检查排序是否合理
	// （因为我们知道排序很可能是
	// ）？
	if orderedList := byPos(list); !sort.IsSorted(orderedList) {
		sort.Sort(orderedList)
	}
}

// CommentMap将AST节点映射到与之关联的注释组列表
// 。有关
// 协会的说明，请参见NewCommentMap。
// 
type CommentMap map[Node][]*CommentGroup

func (cmap CommentMap) addComment(n Node, c *CommentGroup) {
	list := cmap[n]
	if len(list) == 0 {
		list = []*CommentGroup{c}
	} else {
		list = append(list, c)
	}
	cmap[n] = list
}

type byInterval []Node

func (a byInterval) Len() int { return len(a) }
func (a byInterval) Less(i, j int) bool {
	pi, pj := a[i].Pos(), a[j].Pos()
	return pi < pj || pi == pj && a[i].End() > a[j].End()
}
func (a byInterval) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

// 节点列表按源顺序返回AST n的节点列表。
// 
func nodeList(n Node) []Node {
	var list []Node
	Inspect(n, func(n Node) bool {
		// 不收集评论
		switch n.(type) {
		case nil, *CommentGroup, *Comment:
			return false
		}
		list = append(list, n)
		return true
	})
	// 注意：当前的实现假设Inspect首先深入遍历
	// AST，因此_source o顺序。如果AST traversal 
	// 不遵循源顺序，则下面的排序调用将是
	// 必需的。
	// 排序。排序（按时间间隔（列表））
	return list
}

// commentListReader帮助遍历注释组列表。
// 
type commentListReader struct {
	fset     *token.FileSet
	list     []*CommentGroup
	index    int
	comment  *CommentGroup  // 当前索引处的注释组
	pos, end token.Position // 当前索引处注释组的源间隔
}

func (r *commentListReader) eol() bool {
	return r.index >= len(r.list)
}

func (r *commentListReader) next() {
	if !r.eol() {
		r.comment = r.list[r.index]
		r.pos = r.fset.Position(r.comment.Pos())
		r.end = r.fset.Position(r.comment.End())
		r.index++
	}
}

// 节点堆栈跟踪嵌套节点。
// 堆栈上较低的节点在词汇上包含堆栈上较高的节点。
// 
type nodeStack []Node

// push弹出所有在词汇上出现在n 
// 之前的节点，然后在堆栈上按下n。
// 
func (s *nodeStack) push(n Node) {
	s.pop(n.Pos())
	*s = append((*s), n)
}

// pop弹出词法上出现在pos之前的所有节点
// （即词法范围在pos之前或在pos处结束）。
// 返回最后一个弹出的节点。
// 
func (s *nodeStack) pop(pos token.Pos) (top Node) {
	i := len(*s)
	for i > 0 && (*s)[i-1].End() <= pos {
		top = (*s)[i-1]
		i--
	}
	*s = (*s)[0:i]
	return top
}

// NewCommentMap通过将注释列表的注释组
// 与节点指定的AST的节点相关联，创建一个新的注释映射。
// 
// A注释组g与节点n关联，如果：
// 
// /-g在与n结束的同一行上开始
// /-g在紧接着n的行上开始，而且在g之后和下一个节点之前至少有一个空行在n之前开始，并且与n之前的节点没有关联。通过前面的规则，NewCommentMap尝试将注释组与“最大”的节点关联：例如，如果注释是一个行注释
// 跟在赋值后面，则注释与整个
// 赋值相关联，而不仅仅是赋值中的最后一个操作数。
// 
func NewCommentMap(fset *token.FileSet, node Node, comments []*CommentGroup) CommentMap {
	if len(comments) == 0 {
		return nil // 没有要映射的注释
	}

	cmap := make(CommentMap)

	// 设置注释阅读器r 
	tmp := make([]*CommentGroup, len(comments))
	copy(tmp, comments) // 不要更改传入的注释
	sortComments(tmp)
	r := commentListReader{fset: fset, list: tmp} // ！r、 eol（）因为len（comments）>0 
	r.next()

	// 按词法顺序创建节点列表
	nodes := nodeList(node)
	nodes = append(nodes, nil) // 追加sentinel 

	// 设置迭代变量
	var (
		p     Node           // 上一个节点
		pend  token.Position // p的结束
		pg    Node           // 上一个节点组（包含“重要”节点）
		pgend token.Position // pg 
		stack nodeStack      // 节点组的堆栈
	)

	for _, q := range nodes {
		var qpos token.Position
		if q != nil {
			qpos = fset.Position(q.Pos()) // 当前节点位置
		} else {
			// 将伪哨兵位置设置为无穷大，以便
			// 哨兵前处理所有注释
			const infinity = 1 << 30
			qpos.Offset = infinity
			qpos.Line = infinity
		}

		// 在当前节点前处理注释
		for r.end.Offset <= qpos.Offset {
			// 确定最近的节点组
			if top := stack.pop(r.comment.Pos()); top != nil {
				pg = top
				pgend = fset.Position(pg.End())
			}
			// 尝试先将注释与节点组
			// （即“重要”节点，如声明）；
			// 如果失败，请尝试将其与最近的
			// 节点关联。
			// TODO（gri）尝试简化下面的逻辑
			var assoc Node
			switch {
			case pg != nil &&
				(pgend.Line == r.pos.Line ||
					pgend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line):
				// 1）注释从上一个节点组结束的同一行开始，或
				// 2）注释在
				// 上一个节点组之后立即开始，并且在
				// 当前节点
				// =>将注释与上一个节点组
				assoc = pg
			case p != nil &&
				(pend.Line == r.pos.Line ||
					pend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line ||
					q == nil):
				// 相同的规则适用于p而不是pg，
				// 但如果我们在末尾（q==nil）也与p关联
				assoc = p
			default:
				// 否则，将注释与当前节点关联
				if q == nil {
					// 我们只能在没有p 
					// 这意味着没有节点
					panic("internal error: no comments should be associated with sentinel")
				}
				assoc = q
			}
			cmap.addComment(assoc, r.comment)
			if r.eol() {
				return cmap
			}
			r.next()
		}

		// 更新以前的节点
		p = q
		pend = fset.Position(p.End())

		// 如果我们看到“重要”节点
		switch q.(type) {
		case *File, *Field, Decl, Spec, Stmt:
			stack.push(q)
		}
	}

	return cmap
}

// 更新将注释映射中的旧节点替换为新节点
// 并返回新节点。与
// 旧节点关联的注释与新节点关联。
// 
func (cmap CommentMap) Update(old, new Node) Node {
	if list := cmap[old]; len(list) > 0 {
		delete(cmap, old)
		cmap[new] = append(cmap[new], list...)
	}
	return new
}

// Filter返回一个新的注释映射，该映射只包含在
// 条目。
// 节点指定的AST中存在相应节点的cmap的那些
// 
func (cmap CommentMap) Filter(node Node) CommentMap {
	umap := make(CommentMap)
	Inspect(node, func(n Node) bool {
		if g := cmap[n]; len(g) > 0 {
			umap[n] = g
		}
		return true
	})
	return umap
}

// Comments返回注释映射中注释组的列表。
// 结果按源顺序排序。
// 
func (cmap CommentMap) Comments() []*CommentGroup {
	list := make([]*CommentGroup, 0, len(cmap))
	for _, e := range cmap {
		list = append(list, e...)
	}
	sortComments(list)
	return list
}

func summary(list []*CommentGroup) string {
	const maxLen = 40
	var buf bytes.Buffer

	// 收集评论文本
loop:
	for _, group := range list {
		// 注意：CommentGroup。Text（）对我们
		// 需要的内容做了太多的工作，只会替换这个最里面的循环。
		// 直接做就行了。
		for _, comment := range group.List {
			if buf.Len() >= maxLen {
				break loop
			}
			buf.WriteString(comment.Text)
		}
	}

	// 如果太长则截断
	if buf.Len() > maxLen {
		buf.Truncate(maxLen - 3)
		buf.WriteString("...")
	}

	// 用空格替换任何不可见项
	bytes := buf.Bytes()
	for i, b := range bytes {
		switch b {
		case '\t', '\n', '\r':
			bytes[i] = ' '
		}
	}

	return string(bytes)
}

func (cmap CommentMap) String() string {
	// 按排序顺序打印地图条目
	var nodes []Node
	for node := range cmap {
		nodes = append(nodes, node)
	}
	sort.Sort(byInterval(nodes))

	var buf bytes.Buffer
	fmt.Fprintln(&buf, "CommentMap {")
	for _, node := range nodes {
		comment := cmap[node]
		// 打印标识符名称；打印其他节点的节点类型
		var s string
		if ident, ok := node.(*Ident); ok {
			s = ident.Name
		} else {
			s = fmt.Sprintf("%T", node)
		}
		fmt.Fprintf(&buf, "\t%p  %20s:  %s\n", node, s, summary(comment))
	}
	fmt.Fprintln(&buf, "}")
	return buf.String()
}
